﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Plugins;
using StockModel;

namespace PluginSample2.Historical
{
	internal class Engine : IDisposable
	{
		#region Attributes

		private readonly DecisionEngineData _engineData;
		private readonly FakeOrderExecutor _fakeOrderExecutor;

		private IEnumerable<Stock> _stocksToProcess;

		private readonly QuotationDataSource _quotationDataSource;
		private readonly RawStockDataSource _rawStockDataSource;
		private readonly DailyChartGenerator _dailyChartGenerator;

		private readonly List<Operation> _operations;

		#endregion

		#region Properties

		public EngineSettings Settings { get; set; }

		#endregion

		#region Events

		public event EventHandler<HistoricalResultsAvailableEventArgs> ResultsAvailable;

		#endregion

		#region Public Methods

		public Engine(QuotationDataSource quotationDataSource, RawStockDataSource rawStockDataSource, DailyChartGenerator dailyChartGenerator, DecisionEngineData engineData)
		{
			_quotationDataSource = quotationDataSource;
			_rawStockDataSource = rawStockDataSource;
			_dailyChartGenerator = dailyChartGenerator;
			_engineData = engineData;

			_operations = new List<Operation>();

			_fakeOrderExecutor = new FakeOrderExecutor();
		}

		public void Execute()
		{
			_operations.Clear();

			OperationManager operationManager = new OperationManager(this.Settings.StockParameters, _fakeOrderExecutor, this.Settings.InvertLogic);
			operationManager.OperationExecuted += OperationManager_OperationExecuted;
			
			_engineData.State = DecisionEngineState.WaitingOpen;
			
			if (this.Settings.ExecureInParallel)
			{
				
			}
			else
			{
				IEnumerable<HistoricalItemToProcess> itemsToProcess = this.FindItemsToProcess();

				List<FrameworkElement> customData = new List<FrameworkElement>();

				foreach (HistoricalItemToProcess historicalItemToProcess in itemsToProcess)
				{
					this.ProcessItem(customData, historicalItemToProcess, operationManager);
				}

				// Clear memory
				foreach (Stock stock in _stocksToProcess)
				{
					stock.Dispose();
				}
				_stocksToProcess = null;

				this.OnResultsAvailable(_operations, customData);
			}
		}

		protected virtual void OnResultsAvailable(IEnumerable<Operation> operations, IEnumerable<FrameworkElement> customData)
		{
			if (this.ResultsAvailable != null)
			{
				// ReSharper disable PossibleInvalidOperationException
				HistoricalResultsAvailableEventArgs e = new HistoricalResultsAvailableEventArgs(this.Settings.StartDate.Value, this.Settings.EndDate.Value);
				// ReSharper restore PossibleInvalidOperationException

				e.Result.Operations.AddRange(operations);
				e.CustomData.AddRange(customData);
				this.ResultsAvailable(this, e);
			}
		}

		#endregion

		#region Event Handlers

		private void OperationManager_OperationExecuted(object sender, OperationExecutedEventArgs e)
		{
			_operations.Add(e.Operation);
		}

		#endregion

		#region Private Methods

		private IEnumerable<HistoricalItemToProcess> FindItemsToProcess()
		{
			List<HistoricalItemToProcess> result = new List<HistoricalItemToProcess>();

			// ReSharper disable LoopCanBeConvertedToQuery
			_stocksToProcess = _rawStockDataSource.GetStocks(this.Settings.SelectedStockName);
			foreach (Stock stock in _stocksToProcess)
			{
				IEnumerable<DateTime> days = _quotationDataSource.GetAvailableDays(stock);
				foreach (DateTime day in days)
				{
					// Set the max and min days to know the number of days processed
					if (this.Settings.UseAllDays && (this.Settings.StartDate == null || this.Settings.StartDate > day))
					{
						this.Settings.StartDate = day;
					}

					if (this.Settings.UseAllDays && (this.Settings.EndDate == null || this.Settings.EndDate < day))
					{
						this.Settings.EndDate = day;
					}

					if (this.Settings.UseAllDays || (day >= this.Settings.StartDate && day <= this.Settings.EndDate))
					{
						result.Add(new HistoricalItemToProcess(day, stock));
					}
				}
			}
			// ReSharper restore LoopCanBeConvertedToQuery

			return result.OrderBy(r => r.Date);
		}

		private void ProcessItem(ICollection<FrameworkElement> customData, HistoricalItemToProcess itemToProcess, OperationManager operationManager)
		{
			operationManager.StockSymbol = itemToProcess.Stock.Id.Code;

			using (DayProcessor dayProcessor = new DayProcessor(this.Settings, itemToProcess.Date, itemToProcess.Stock, _quotationDataSource, _rawStockDataSource, _fakeOrderExecutor, operationManager, _engineData))
			{
				dayProcessor.Execute();

				if (this.Settings.GenerateDailyCharts)
				{
					DateTime currentDate = itemToProcess.Date;
					List<Operation> dailyOperations = _operations.Where(o => o.SellDate.Date == currentDate).ToList();
					if (!this.Settings.ChartsOnlyOnOperatedDays || this.Settings.ChartsOnlyOnOperatedDays && dailyOperations.Count > 0)
					{
						FrameworkElement dailyChart = _dailyChartGenerator.GenerateChart(dailyOperations, dayProcessor.Bars.Where(b => b.Minimum != 0), currentDate, this.Settings.ShowOperations);
						customData.Add(dailyChart);
					}
				}
			}
		}

		#endregion

		#region IDisposable

		protected bool Disposed { get; private set; }

		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				if (_stocksToProcess != null)
				{
					foreach (Stock stock in _stocksToProcess)
					{
						stock.Dispose();
					}
				}
			}

			// Dispose here only unmanaged objects 
			// Don’t use managed objects here because maybe 
			// they have been finalized already
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~Engine()
		{
			this.Dispose(false);
		}

		#endregion
	}
}
